<?php


namespace app\command\count;


use app\common\model\CountDay;
use app\common\model\MemberInviteModel;
use app\common\model\MemberLoginModel;
use app\common\model\MemberModel;
use app\common\model\MoneyCashModel;
use app\common\model\MoneyRecordModel;
use org\DingDingRobot;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\Output;

use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;

class CountDayCommand extends Command
{
    protected function configure()
    {
        $this->setName('count_day')
            ->addArgument('begin_day', Argument::OPTIONAL, '指定执行开始日期')
            ->addArgument('end_day', Argument::OPTIONAL, '指定执行截至日期')
            ->setDescription('每日统计脚本');
    }

    /**
     * @param Input $input
     * @param Output $output
     * @return int|void|null
     */
    protected function execute(Input $input, Output $output)
    {
        $begin_day = $input->getArgument('begin_day');
        $end_day   = $input->getArgument('end_day');
        // 新增用户
        if (empty($end_day) && empty($begin_day)) {

            # 定时任务配置在凌晨之后，默认跑前一天的数据
            $date = date('Y-m-d', strtotime(date('Y-m-d') . ' -1 day'));
            $this->buildData($date, $output);

        } elseif (!empty($begin_day) && !empty($end_day)) {

            # 跑指定日期范围的数据
            $begin_time = strtotime(date('Y-m-d 00:00:00', strtotime($begin_day)));
            $end_time   = strtotime(date('Y-m-d 23:59:59', strtotime($end_day)));

            for ($i = $begin_time; $i < $end_time; $i += 86400) {
                $date = date('Y-m-d', $i);
                $this->buildData($date, $output);
                $output->info($date);
            }

        } elseif (!empty($begin_day)) {
            # 跑指定日期的数据
            $date = date('Y-m-d', strtotime($begin_day));
            $this->buildData($date, $output);
            $output->info($date);
        }
    }

    /**
     * @param string $date
     * @param Output $output
     * 1、新增：以当天注册表新增的设备ID为准
     * 2、注册：当天游客转正式的用户数
     * 3、注册率：注册/新增
     * 4、邀请数：当天新增绑定上线数（需讨论），
     * 4、邀请比：邀请数/新增数
     * 5、日活：当天启动用户数（需讨论）
     * 6、非新增日活：（日活 - 新增）/日活
     * 7、次日留存、7日留存：双击该位置，手动输入            *
     * 8、参与人数：当天领取过金币的人数
     * 9、参与率：参与人数/日活
     * 10、金币发放：当天共发放多少金币
     * 11、人均值：金币数/参与人数
     * 12、钻石发放：当天共发放多少钻石
     * 13、人均值：钻石数/参与人数
     * 14、碎片发放：当天共发放多少碎片
     * 15、人均值：碎片数/参与人数
     * 16、广告收益：双击手动输入                         *
     * 17、提现金额：双击手动输入                         *
     * 18、总笔数：当天共提现的笔数（包含未成功，需去除重复人数）
     * 19、成功笔数：当天提现成功笔数
     * 20、过审率：成功笔数/总笔数
     * 21、新人提现：新人专享额度提现成功笔数
     * 22、提现率：新人提现/新增用户
     * 23、推广成功：手动输入                            *
     * 24、毛利：广告收入 - 提现成本 - 推广成本
     */
    protected function buildData(string $date, Output $output)
    {
        try {
            // 用户数据
            $mbData = $this->memberData($date);
            // 用户留存
            $keepUser = $this->keepUser($date);
            // 提现数据
            $cashData = $this->cashCensus($date);
            // 流水统计
            $fundCensus = $this->fundCensus($date);
            // 新人数据
            $rewardData = $this->rewardData($date);
            // 数据处理
            $todayCensusData = [
                "record_date"            => $date,// 统计日期
                "new"                    => $mbData['newUser'], // 新增
                "register"               => $mbData['bind'], // 注册
                "register_rate"          => $this->bcDivRate($mbData['bind'], $mbData['newUser']), // 注册率
                "invite_num"             => $mbData['todayInvite'], // 邀请数量
                "invite_rate"            => $this->bcDivRate($mbData['todayInvite'], $mbData['newUser']), // 邀请率
                "extra_invite"           => $mbData['extraInvite'],// 补邀请
                "daily_life"             => $mbData['active'], // 日活
                "non_new_life"           => $this->bcDivRate(($mbData['active'] - $mbData['newUser']), $mbData['active']), // 非新增日活
                "next_day_save"          => $keepUser['day1'], // 次日留存
                "three_day_save"         => $keepUser['day3'], // 三日留存
                "seven_day_save"         => $keepUser['day7'], // 七日留存
                "user_num"               => $fundCensus['receive'], // 参与人数
                "participation_pate"     => $this->bcDivRate($fundCensus['receive'], $mbData['active']), // 参与率
                "gold_num"               => $fundCensus['coin'], // 当天金币发放总数
                "gold_avg_num"           => $fundCensus['avgCoin'],// 非邀请人均金币
                "diamond_num"            => $fundCensus['diamond'], // 钻石数量
                "diamond_avg_num"        => $fundCensus['avgDiamond'],// 非邀请人均钻石
                "invite_coin"            => $fundCensus['inviteCoin'],// 邀请金币数量
                "invite_diamond"         => $fundCensus['inviteDiamond'],// 邀请钻石
                "withdraw_user_num"      => $cashData['cashNum'], // 提现总笔数
                "success_num"            => $cashData['cashOk'], // 提现成功笔数
                "audit_rate"             => $this->bcDivRate($cashData['cashOk'], $cashData['cashNum']), // 过审率
                "reward"                 => $rewardData['newReward'],// 领取新人礼人数
                "reward_rate"            => $this->bcDivRate($rewardData['newReward'], $mbData['newUser']),// 新人礼领取率
                "new_user_withdraw"      => $rewardData['newCash'], // 新人提现笔数
                "new_user_withdraw_rate" => $this->bcDivRate($rewardData['newCash'], $rewardData['newReward']), // 新人提现率
                "create_time"            => time(),
            ];
            $row = CountDay::where("record_date", $date)->find();
            if ($row) {
                $row->where(['id' => $row['id']])->update($todayCensusData);
            } else {
                CountDay::insert($todayCensusData);
            }
            DingDingRobot::getInstance()->pushText("每日统计脚本,执行完成,日期:{$date}");
        } catch (DataNotFoundException $e) {
            DingDingRobot::getInstance()->pushText("每日统计脚本异常，日期:{$date},内容：" . $e->getMessage());
        } catch (ModelNotFoundException $e) {
            DingDingRobot::getInstance()->pushText("每日统计脚本异常，日期:{$date},内容：" . $e->getMessage());
        } catch (DbException $e) {
            DingDingRobot::getInstance()->pushText("每日统计脚本异常，日期:{$date},内容：" . $e->getMessage());
        }
    }

    /**
     * 新人数据
     * @param string $date
     * @return array
     */
    protected function rewardData(string $date)
    {
        // 新人礼
        $memberIds = MoneyRecordModel::where([$this->betweenDate($date), ['method', '=', 11]])->column('distinct member_id');
        // 新人提现
        $newCash = MoneyCashModel::where([$this->betweenDate($date), ['member_id', 'in', $memberIds], ['state', '=', 3]])
            ->value('count(member_id)');
        return ['newReward' => count($memberIds), 'newCash' => $newCash];
    }

    /**
     * 用户统计数据
     * @param string $date
     * @return array
     */
    protected function memberData(string $date)
    {
        // 添加时间
        $createTime = $this->betweenDate($date);
        // 筛选时间
        $relationTime = $this->betweenDate($date, 'relation_time');
        // 当天总邀请
        $todayAllInvite = MemberModel::where([$relationTime])->column('id');
        // 新增用户
        $newUser = MemberModel::where([$createTime])->value('count(id)');
        // 当天邀请用户
        $todayInvite = MemberModel::where([$createTime, $relationTime])->column('id');
        // 当天注册的用户
        $todayBind = MemberModel::where([$createTime, $this->betweenDate($date, 'bind_time')])->value('count(id)');
        // 日活统计
        $dayActive = MemberLoginModel::where([$createTime])->value('count(distinct member_id)');
        // 补邀请数量
        $diffInvite = array_diff($todayAllInvite, $todayInvite);
        // 用户数据
        return ['newUser' => $newUser, 'todayInvite' => count($todayInvite), 'extraInvite' => count($diffInvite),
            'bind' => $todayBind, 'active' => $dayActive];
    }

    /**
     * 用户留存统计
     * @param string $date
     * @return array|int[]
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    protected function keepUser(string $date)
    {
        // 初始化
        $returnData = ['day1' => 0, 'day3' => 0, 'day7' => 0];
        // 筛选结果
        $loginUser = MemberLoginModel::where([$this->betweenDate($date)])->group(['member_id'])->column('create_time, member_id');
        // 数据拆分
        $loginUser = array_chunk($loginUser, 2000);
        // 数据处理
        empty($loginUser) || array_walk($loginUser, function ($rows) use (&$returnData) {
            $memberIds = array_column($rows, 'member_id');// 用户ID
            $loginData = array_column($rows, null, 'member_id'); // 登录数据
            // 获取用户注册日期
            $memberList = MemberModel::where(['id' => $memberIds])->field(['create_time', 'id'])->select()->toArray();
            $memberList = array_column($memberList, null, 'id');
            // 统计留存
            foreach ($loginData as $member) {
                $diffDay = $this->dayDiff($member['create_time'], $memberList[$member['member_id']]['create_time'] ?? '');
                $diffDay == 1 && $returnData['day1'] += 1; // 1日留存
                $diffDay == 3 && $returnData['day3'] += 1; // 3日留存
                $diffDay == 7 && $returnData['day7'] += 1; // 7日留存
            }
        });
        return $returnData;
    }

    /**
     * 提现统计
     * @param string $date
     * @return array|int[]
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    protected function cashCensus(string $date)
    {
        $returnData = ['cashNum' => 0, 'cashOk' => 0];
        // 筛选字段
        $fields = ['count(distinct member_id) cashNum', 'count(case when state=3 then id end) cashOk'];
        // 数据处理
        if ($cashData = MoneyCashModel::where([$this->betweenDate($date)])->field($fields)->find()) {
            $returnData = $cashData->toArray();
        }
        return $returnData;
    }

    /**
     * 资金流水统计
     * @param string $date
     * @return MoneyRecordModel|array|\think\Model|null
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    protected function fundCensus(string $date)
    {
        // 筛选时间
        $createTime = $this->betweenDate($date);
        // 初始化
        $returnData = ['receive' => 0, 'coin' => 0, 'diamond' => 0, 'avgCoin' => 0, 'avgDiamond' => 0, 'inviteCoin' => 0,
            'inviteDiamond' => 0];
        // 筛选字段
        $fields = ['count(distinct member_id) receive', // 领取人数
            'sum(if(currency=0, amount, 0)) coin', // 金币发放总数
            'sum(if(currency=3, amount, 0)) diamond', // 钻石发放总数
        ];
        // 筛选结果
        if ($fundData = MoneyRecordModel::where([$createTime, ['method', 'not in', [2, 8, 9, 10, 12]]])->field($fields)->find()) {
            $returnData = $fundData->toArray();
            $returnData['avgCoin'] = floor($fundData['coin'] / $fundData['receive']);
            $returnData['avgDiamond'] = floor($fundData['diamond'] / $fundData['receive']);
        }
        // 邀请数据
        $inviteFields = ['sum(if(currency=0, amount, 0)) icon', 'sum(if(currency=3, amount, 0)) diamond'];
        if ($inviteData = MemberInviteModel::where([$createTime, ['type', 'in', [1, 2, 3]]])->field($inviteFields)->find()) {
            $returnData['inviteCoin'] = $inviteData['icon'];
            $returnData['inviteDiamond'] = $inviteData['diamond'];
        }
        return $returnData;
    }

    /**
     * 日期计算
     * @param string $startDate
     * @param string $endDate
     * @return int
     */
    protected function dayDiff(string $startDate, string $endDate)
    {
       return date_diff(date_create(substr($startDate, 0, 10)), date_create(substr($endDate, 0, 10)))->d;
    }

    /**
     * 时间筛选
     * @param string $date
     * @param string $field
     * @return array[]
     */
    protected function betweenDate(string $date, string $field = 'create_time')
    {
        return [$field, 'between', betweenDate($date . '~' . $date)];
    }

    /**
     * 除数百分百
     * @param $a
     * @param $b
     * @param int $scale
     * @return int|string
     */
    protected function bcDivRate($a, $b, $scale = 2)
    {
        $arr = [0, '0.00', 0.00];
        if (in_array($a, $arr) || in_array($b, $arr)) {
            return 0;
        }
        return (bcdiv($a, $b, $scale) * 100) . '%';
    }
}

